﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;

namespace ZerAx.Net
{
    public class RingBuffer : IEnumerable<byte>
    {
        private byte[] data;
        private int start, end;

        public byte this[int idx]
        {
            get { return data[Index(start + idx)]; }
            set { data[Index(start + idx)] = value; }
        }

        public int Capacity => data.Length;
        public int Length => start <= end ? end - start : data.Length - start + end;
        public int FreeSpace => data.Length - Length;

        public RingBuffer(int capacity)
        {
            data = new byte[capacity];
            start = 0;
            end = 0;
        }

        public byte[] GetBuffer() => this.AsEnumerable().ToArray();

        public int Read(byte[] bytes) => Read(bytes, 0, bytes.Length);

        public int Read(byte[] buffer, int offset, int count)
        {
            if (start <= end)
            {
                var len = Math.Min(end - start, count);
                Array.Copy(data, start, buffer, offset, len);
                start += len;
                return len;
            }
            else
            {
                var upperLen = data.Length - start;
                if (count <= upperLen)
                {
                    Array.Copy(data, start, buffer, offset, count);
                    start += count;
                    return count;
                }
                else
                {
                    var len = Math.Min(upperLen + end, count);
                    Array.Copy(data, start, buffer, offset, upperLen);
                    Array.Copy(data, 0, buffer, Index(offset + upperLen), len - upperLen);
                    start = Index(start + len);
                    return len;
                }
            }
        }

        public void Write(byte[] bytes) => Write(bytes, 0, bytes.Length);
        public void Write(byte[] buffer, int offset, int count)
        {
            if (count >= FreeSpace)
            {
                
                var total = Length + count;
                Expand(total + (total >> 1));
            }

            if (start <= end)
            {
                var upperFreeSpace = data.Length - end;
                if (count <= upperFreeSpace)
                {
                    Array.Copy(buffer, offset, data, end, count);
                }
                else
                {
                    Array.Copy(buffer, offset, data, end, upperFreeSpace);
                    Array.Copy(buffer, offset + upperFreeSpace, data, 0, count - upperFreeSpace);
                }
            }
            else
            {
                Array.Copy(buffer, offset, data, end, count);
            }

            end = Index(end + count);
        }

        public void Add(byte b)
        {
            data[end] = b;
            end = Index(end + 1);
        }

        public void Expand(int capacity)
        {
            var temp = new byte[capacity];
            Read(temp, 0, temp.Length);
            start = 0;
            end = Length;
            data = temp;
        }

        private int Index(int idx)
        {
            return idx % data.Length;
        }

        public IEnumerator<byte> GetEnumerator()
        {
            for (int i = 0; i < Length; i++)
            {
                yield return this[i];
            }
        }

        IEnumerator IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }
    }
}
